/**
 * libnlog -- User friendly logging system with network support.
 *
 * Copyright (C) 2011
 * 	Samuel Nussbaum <samuel (dot) nussbaum (at) e-robot.ch>
 *
 * This library is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 */
#include "NetworkLogger.hpp"

#include <vector>

#include <boost/thread.hpp>
#include <boost/thread/thread_time.hpp>
#include <boost/functional/hash/extensions.hpp>
#include <boost/unordered_set.hpp>

#include <boost/asio.hpp>
#include <boost/asio/basic_datagram_socket.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/basic_endpoint.hpp>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

#include <boost/date_time/posix_time/time_serialize.hpp>

using namespace boost;
using namespace boost::posix_time;
using namespace boost::asio;

namespace nlog {

Thread *NetworkLogger::m_thread = NULL;

NetworkLogger::Service *NetworkLogger::m_service = new Service();

NetworkLogger &NetworkLogger::instance(const String &_name) {
	Logger *base = &Logger::instance(_name);
	NetworkLogger *instance = dynamic_cast<NetworkLogger *>(base);
	if (instance == NULL) {
		instance = new NetworkLogger(*base);
		m_instances->erase(base);
		m_instances->insert(instance);
		delete base;
	}
	return *instance;
}

void NetworkLogger::write(const Message &_message) const {
	if (m_socket != NULL && _message.level() >= m_level) {
		m_mutex->lock();
		std::basic_string<char> resultStr;
		std::basic_ostringstream<char> dataStr;
		archive::text_oarchive archStr(dataStr);
		archStr << _message;
		resultStr = dataStr.str();
		m_socket->send(asio::buffer(resultStr));
//		std::cout << resultStr << std::endl;
		m_mutex->unlock();
	}
}


NetworkLogger &NetworkLogger::sendTo(const String &_address, short _port) {
	m_mutex->lock();
	if (m_endpoint != NULL)
		delete m_endpoint;
	m_endpoint = new Endpoint(Address::from_string(_address), _port);
	if (m_socket != NULL)
		delete m_socket;
	m_socket = new Socket(*m_service, m_endpoint->protocol());
	m_socket->connect(*m_endpoint);
	m_mutex->unlock();
	return *this;
}

void NetworkLogger::receiveFrom(const String &_address, const String &_multicast, short _port) {
	stop();
	m_thread = new Thread(NetworkLogger::run, _address, _multicast, _port);
}

void NetworkLogger::stop() {
	if (m_thread != NULL) {
		m_thread->interrupt();
		while (m_thread->joinable()) {
			m_thread->timed_join(milliseconds(10));
		}
		delete m_thread;
		m_thread = NULL;
	}
}

void NetworkLogger::run(String _address, String _multicast, short _port) {
	Endpoint endpoint(Address::from_string(_address), _port);
	Socket socket(*m_service);

	socket.open(endpoint.protocol());
	socket.set_option(Socket::reuse_address(true));
	socket.bind(endpoint);
	socket.set_option(ip::multicast::join_group(Address::from_string(_multicast)));

	Message message(None, "Main");
	std::vector<char> data(65535);
	while (true) {
		SizeT size = socket.receive(asio::buffer(data));
		std::basic_string<char> resultStr(&data[0], size);
		std::basic_istringstream<char> dataStr(resultStr);
		archive::text_iarchive archStr(dataStr);
		archStr >> message;
		Logger::instance(message.name()).Logger::write(message);
	}
}

}
